﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using JXT.PrimaryKey.Batman.Extensions;

namespace JXT.PrimaryKey.Batman.Domain.Repositories.MySql.Expressions
{
    internal class WhereExpressionVisitor : ExpressionVisitor {
        private Expression expression;
        private WhereQuery query = new WhereQuery();

        public WhereExpressionVisitor(Expression exp) {
            expression = exp;
        }

        public WhereQuery GetQuery() {
            Visit(expression);
            return query;
        }

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            parseCallItem(m);
            //return base.VisitMethodCall(m);
            return m;
        }

        protected override Expression VisitBinary(BinaryExpression b) {
            if (b.NodeType == ExpressionType.And || b.NodeType == ExpressionType.AndAlso) {
                WhereExpressionVisitor leftVisitor = new WhereExpressionVisitor(b.Left);
                query.And(leftVisitor.GetQuery());
                WhereExpressionVisitor rightVisitor = new WhereExpressionVisitor(b.Right);
                query.And(rightVisitor.GetQuery());
            } else if (b.NodeType == ExpressionType.Or || b.NodeType == ExpressionType.OrElse) {
                WhereExpressionVisitor leftVisitor = new WhereExpressionVisitor(b.Left);
                query.Or(leftVisitor.GetQuery());
                WhereExpressionVisitor rightVisitor = new WhereExpressionVisitor(b.Right);
                query.Or(rightVisitor.GetQuery());
            } else {
                return visitWherePart(b);
            }
            return b;
        }

        private Expression visitWherePart(BinaryExpression b) {
            switch (b.NodeType) {
                default:
                    return base.VisitBinary(b);
                case ExpressionType.LessThan:
                case ExpressionType.LessThanOrEqual:
                case ExpressionType.GreaterThan:
                case ExpressionType.GreaterThanOrEqual:
                case ExpressionType.Equal:
                case ExpressionType.NotEqual:
                case ExpressionType.Not:
                    parseWhereItem(b);
                    return b;
            }
        }

        private void parseCallItem(MethodCallExpression m)
        {
            if (m.Object.Type == typeof(string))
            {
                if (m.Method.Name == "Contains")
                {
                    var value = getValue(m.Arguments[0]);
                    ColumnExpressionVisitor columnVisitor = new ColumnExpressionVisitor(m.Object);
                    string parameterName = getParameter(m.Object as MemberExpression);
                    query = new WhereQuery();
                    query.Init(String.Format("{0} like {1}",
                        columnVisitor.GetColumnString(),
                        parameterName),
                        ParameterFactory.Create(parameterName, String.Format("%{0}%", value)));
                }
                else if (m.Method.Name == "StartsWith")
                {
                    var value = getValue(m.Arguments[0]);
                    ColumnExpressionVisitor columnVisitor = new ColumnExpressionVisitor(m.Object);
                    string parameterName = getParameter(m.Object as MemberExpression);
                    query = new WhereQuery();
                    query.Init(String.Format("{0} like {1}",
                        columnVisitor.GetColumnString(),
                        parameterName),
                        ParameterFactory.Create(parameterName, String.Format("{0}%", value)));
                }
                else if (m.Method.Name == "EndWith")
                {
                    var value = getValue(m.Arguments[0]);
                    ColumnExpressionVisitor columnVisitor = new ColumnExpressionVisitor(m.Object);
                    string parameterName = getParameter(m.Object as MemberExpression);
                    query = new WhereQuery();
                    query.Init(String.Format("{0} like {1}",
                        columnVisitor.GetColumnString(),
                        parameterName),
                        ParameterFactory.Create(parameterName, String.Format("%{0}", value)));
                }
            }
        }

        private object getValue(Expression exp)
        {
            ValueExpressionVisitor valueVisitor = new ValueExpressionVisitor(exp);
            return valueVisitor.GetValue();
        }

        private void parseWhereItem(BinaryExpression b) {
            ColumnExpressionVisitor columnVisitor = new ColumnExpressionVisitor(b.Left);
            ValueExpressionVisitor valueVisitor = new ValueExpressionVisitor(b.Right);
            query = new WhereQuery();
            string parameterName;
            if (b.Left is UnaryExpression)
            {
                parameterName = String.Format("{0}_{1}", getParameter(((b.Left as UnaryExpression).Operand as MemberExpression)), Guid.NewGuid().ToGuidString());
            }
            else
            {
                parameterName = String.Format("{0}_{1}", getParameter(b.Left as MemberExpression), Guid.NewGuid().ToGuidString());
            }
            var value = valueVisitor.GetValue();
            var valueType = value.GetType();
            if (valueType.IsEnum)
            {
                value = Convert.ChangeType(value, valueType.GetEnumUnderlyingType());
                //value = Convert.ToInt32(value);
            }
            query.Init(String.Format("{0} {1} {2}",
                columnVisitor.GetColumnString(),
                getOperator(b),
                parameterName),
                ParameterFactory.Create(parameterName, value));
        }

        private string getOperator(BinaryExpression b) {
            switch (b.NodeType) {
                default:
                    return String.Empty;
                case ExpressionType.LessThan:
                    return DbEnviroumentFactory.Instanse.LessThanTag;
                case ExpressionType.LessThanOrEqual:
                    return DbEnviroumentFactory.Instanse.LessThanOrEqualTag;
                case ExpressionType.GreaterThan:
                    return DbEnviroumentFactory.Instanse.GreaterThanTag;
                case ExpressionType.GreaterThanOrEqual:
                    return DbEnviroumentFactory.Instanse.GreaterThanOrEqualTag;
                case ExpressionType.Equal:
                    return DbEnviroumentFactory.Instanse.EqualTag;
                case ExpressionType.NotEqual:
                    return DbEnviroumentFactory.Instanse.NotEqualTag;
            }
        }

        private string getParameter(MemberExpression m) {
            return DbEnviroumentFactory.Instanse.ParameterChar + m.Member.Name.ToLower();
        }
    }
}
